package com.symaster.common.geom.util;

import cn.hutool.core.util.ObjectUtil;
import com.symaster.common.animation.nest4j.Nest;
import com.symaster.common.animation.nest4j.data.NestPath;
import com.symaster.common.animation.nest4j.data.Placement;
import com.symaster.common.animation.nest4j.util.Config;
import com.symaster.common.animation.nest4jv2.data.Segment;
import com.symaster.common.clipper.Clipper;
import com.symaster.common.clipper.DefaultClipper;
import com.symaster.common.clipper.Path;
import com.symaster.common.clipper.Paths;
import com.symaster.common.geom.entity.LineSegment;
import com.symaster.common.geom.entity.Point;
import com.symaster.common.geom.entity.Polygon;
import com.symaster.common.geom.entity.Vector;
import com.symaster.common.util.IOUtil;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;

import java.awt.*;
import java.awt.geom.*;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 坐标点工具类
 *
 * @author yinmiao
 * @version 2021-04-30 09:37
 */
public class PointUtil {

    static {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }

    public static void main(String[] args) {
        Point point = translatePoint(new Point(0, 0), 135, 100);
        System.out.println(point);
    }

    public static Point translatePoint(Point point, double angle, double len) {
        double[] doubles = translatePoint(point.getX(), point.getY(), angle, len);
        return new Point(doubles[0], doubles[1]);
    }

    public static double[] translatePoint(double x, double y, double angle, double len) {
        double radians = Math.toRadians(angle + 180);
        return new double[]{len * (- Math.sin(radians)) + x, len * Math.cos(radians) + y};
    }

    public static void autoLayout(double width, double height, double marge, Polygon... polygons) {
        Map<Integer, Polygon> polygonMapByBId = Arrays.stream(polygons).collect(Collectors.toMap(Polygon::getBId, e -> e));

        List<NestPath> parts = Arrays.stream(polygons)
                .peek(e -> e.rotateTransform(new Point(width, 0), -90))
                .map(Polygon::toNestPath).collect(Collectors.toList());

        for (int i = 0; i < parts.size(); i++) {
            parts.get(i).setId(i);
        }

        NestPath binPath = new NestPath();
        binPath.add(0, 0);
        binPath.add((height), 0);
        binPath.add((height), (width));
        binPath.add(0, (width));

        Config config = new Config();
        config.SPACING = marge;
        config.MUTATION_RATE = 80;
        // config.POPULATION_SIZE = 1000;
        Nest nest = new Nest(binPath, parts, config, 1);
        List<List<Placement>> lists = nest.startNest();

        for (List<Placement> list : lists) {
            for (Placement placement : list) {
                Polygon polygon = polygonMapByBId.get(placement.bid);
                if (placement.rotate != 0.0D) {
                    polygon.rotateTransform(new Point(0, 0), placement.rotate);
                }
                polygon.translate(placement.translate.x, placement.translate.y);
            }
        }
        for (Polygon polygon : polygons) {
            polygon.rotateTransform(new Point(0, (width)), 90);
            polygon.translate(0, -width);
        }
    }

    public static void autoLayoutV2(double width, double height, double marge, Polygon... polygons) {
        Map<Integer, Polygon> polygonMapByBId = Arrays.stream(polygons).collect(Collectors.toMap(Polygon::getBId, e -> e));
        List<com.symaster.common.animation.nest4jv2.data.NestPath> parts = Arrays.stream(polygons)
                .peek(e -> e.rotateTransform(new Point(width, 0), -90))
                .map(e -> {

                    double[] xpoints = e.getXpoints();
                    double[] ypoints = e.getYpoints();

                    com.symaster.common.animation.nest4jv2.data.NestPath nestPath = new com.symaster.common.animation.nest4jv2.data.NestPath();
                    for (int i = 0; i < xpoints.length; i++) {
                        nestPath.add(new Segment(xpoints[i], ypoints[i]));
                    }
                    nestPath.bid = e.getBId();
                    return nestPath;
                }).collect(Collectors.toList());

        for (int i = 0; i < parts.size(); i++) {
            parts.get(i).setId(i);
        }

        com.symaster.common.animation.nest4jv2.data.NestPath binPath = new com.symaster.common.animation.nest4jv2.data.NestPath();
        binPath.add(0, 0);
        binPath.add((height), 0);
        binPath.add((height), (width));
        binPath.add(0, (width));

        com.symaster.common.animation.nest4jv2.util.Config config = new com.symaster.common.animation.nest4jv2.util.Config();
        config.SPACING = marge;
        // config.POPULATION_SIZE = 1000;
        com.symaster.common.animation.nest4jv2.Nest nest = new com.symaster.common.animation.nest4jv2.Nest(binPath, parts, config, 1);
        List<List<com.symaster.common.animation.nest4jv2.data.Placement>> lists = nest.startNest();

        for (List<com.symaster.common.animation.nest4jv2.data.Placement> list : lists) {
            for (com.symaster.common.animation.nest4jv2.data.Placement placement : list) {
                Polygon polygon = polygonMapByBId.get(placement.bid);
                if (placement.rotate != 0.0D) {
                    polygon.rotateTransform(new Point(0, 0), placement.rotate);
                }
                polygon.translate(placement.translate.x, placement.translate.y);
            }
        }
        for (Polygon polygon : polygons) {
            polygon.rotateTransform(new Point(0, (width)), 90);
            polygon.translate(0, -width);
        }
    }

    public static double angle(LineSegment lineSegment) {
        Point startPoint = lineSegment.getStartPoint();
        Point endPoint = lineSegment.getEndPoint();
        return Math.atan2(endPoint.getY() - startPoint.getY(), endPoint.getX() - startPoint.getX()) * 180 / Math.PI;
    }

    public static Polygon outOffset(Polygon polygon, double length) {

        Polygon p = clockwise(polygon) ? polygon.clone() : reversal(polygon);
        LinkedList<Point> result = new LinkedList<>();

        LineSegment line = p.lineIterator(false);
        LineSegment outLine = getOutLine(line, length);
        result.add(outLine.getStartPoint());
        result.add(outLine.getEndPoint());
        while ((line = line.getNext()) != null) {
            LineSegment out = getOutLine(line, length);
            result.add(out.getStartPoint());
            result.add(out.getEndPoint());
        }
        return points2polygon(result);
    }

    private static LineSegment getOutLine(LineSegment lineSegment, double length) {

        double angle = angle(lineSegment);

        LineSegment clone = ObjectUtil.clone(lineSegment);
        clone.rotate(clone.center(), -angle);
        clone.getStartPoint().setX(clone.getStartPoint().getX() - length / 2);
        clone.getEndPoint().setX(clone.getEndPoint().getX() + length / 2);
        clone.translate(0, -length);

        Point point = rotateTransform(clone.center(), lineSegment.center(), angle);
        Vector translateVector = getTranslateVector(clone.center(), point);
        clone.translate(translateVector);
        return clone;
    }

    public static Point findIntersection(LineSegment line1, LineSegment line2) {
        double x1 = line1.getStartPoint().x;
        double y1 = line1.getStartPoint().y;
        double x2 = line1.getEndPoint().x;
        double y2 = line1.getEndPoint().y;
        double x3 = line2.getStartPoint().x;
        double y3 = line2.getStartPoint().y;
        double x4 = line2.getEndPoint().x;
        double y4 = line2.getEndPoint().y;

        double denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);

        if (denominator == 0) {
            // The lines are parallel or coincident
            return null;
        }

        double xIntersection = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / denominator;
        double yIntersection = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / denominator;

        // Check if the intersection point lies within the line segments
        if (isPointWithinLineSegment(xIntersection, yIntersection, line1) &&
                isPointWithinLineSegment(xIntersection, yIntersection, line2)) {
            return new Point(xIntersection, yIntersection);
        }

        return null; // Intersection point lies outside the line segments
    }

    private static boolean isPointWithinLineSegment(double x, double y, LineSegment line) {
        double minX = Math.min(line.getStartPoint().x, line.getEndPoint().x);
        double maxX = Math.max(line.getStartPoint().x, line.getEndPoint().x);
        double minY = Math.min(line.getStartPoint().y, line.getEndPoint().y);
        double maxY = Math.max(line.getStartPoint().y, line.getEndPoint().y);
        return (x >= minX && x <= maxX && y >= minY && y <= maxY);
    }

    public static Point getCrossPoint(LineSegment line1, LineSegment line2) {
        double x;
        double y;
        double x1 = line1.getStartPoint().getX();
        double y1 = line1.getStartPoint().getY();
        double x2 = line1.getEndPoint().getX();
        double y2 = line1.getEndPoint().getY();
        double x3 = line2.getStartPoint().getX();
        double y3 = line2.getStartPoint().getY();
        double x4 = line2.getEndPoint().getX();
        double y4 = line2.getEndPoint().getY();
        double k1 = Double.MAX_VALUE;
        double k2 = Double.MAX_VALUE;
        boolean flag1 = false;
        boolean flag2 = false;

        if ((x1 - x2) == 0) {
            flag1 = true;
        }
        if ((x3 - x4) == 0) {
            flag2 = true;
        }

        if (!flag1) {
            k1 = (y1 - y2) / (x1 - x2);
        }
        if (!flag2) {
            k2 = (y3 - y4) / (x3 - x4);
        }

        if (k1 == k2) {
            return null;
        }

        if (flag1) {
            x = x1;
            if (k2 == 0) {
                y = y3;
            } else {
                y = k2 * (x - x4) + y4;
            }
        } else if (flag2) {
            x = x3;
            if (k1 == 0) {
                y = y1;
            } else {
                y = k1 * (x - x2) + y2;
            }
        } else {
            if (k1 == 0) {
                y = y1;
                x = (y - y4) / k2 + x4;
            } else if (k2 == 0) {
                y = y3;
                x = (y - y2) / k1 + x2;
            } else {
                x = (k1 * x2 - k2 * x4 + y4 - y2) / (k1 - k2);
                y = k1 * (x - x2) + y2;
            }
        }
        if (between(x1, x2, x) && between(y1, y2, y) && between(x3, x4, x) && between(y3, y4, y)) {
            return new Point(x, y);
        } else {
            return null;
        }
    }

    public static boolean getCrossPoint(LineSegment line, Point point) {
        return line.onSegment(point);
    }

    public static boolean between(double a, double b, double target) {
        return target >= a - 0.000000001 && target <= b + 0.000000001 || target <= a + 0.000000001 && target >= b - 0.000000001;
    }

    public static boolean judge(LineSegment line1, LineSegment line2) {
        if ((Math.max(line1.getStartPoint().getX(), line1.getEndPoint().getX()) >= Math.min(line2.getStartPoint().getX(), line2.getEndPoint().getX()) && Math.min(line1.getStartPoint().getX(), line1.getEndPoint().getX()) <= Math.max(line2.getStartPoint().getX(), line2.getEndPoint().getX())) &&
                (Math.max(line1.getStartPoint().getY(), line1.getEndPoint().getY()) >= Math.min(line2.getStartPoint().getY(), line2.getEndPoint().getY()) && Math.min(line1.getStartPoint().getY(), line1.getEndPoint().getY()) <= Math.max(line2.getStartPoint().getY(), line2.getEndPoint().getY()))) {
            return ((line2.getStartPoint().getX() - line1.getStartPoint().getX()) * (line1.getEndPoint().getY() - line1.getStartPoint().getY()) - (line2.getStartPoint().getY() - line1.getStartPoint().getY()) * (line1.getEndPoint().getX() - line1.getStartPoint().getX())) *          // 判断B是否跨过A
                    ((line2.getEndPoint().getX() - line1.getStartPoint().getX()) * (line1.getEndPoint().getY() - line1.getStartPoint().getY()) - (line2.getEndPoint().getY() - line1.getStartPoint().getY()) * (line1.getEndPoint().getX() - line1.getStartPoint().getX())) <= 0 &&
                    ((line1.getStartPoint().getX() - line2.getStartPoint().getX()) * (line2.getEndPoint().getY() - line2.getStartPoint().getY()) - (line1.getStartPoint().getY() - line2.getStartPoint().getY()) * (line2.getEndPoint().getX() - line2.getStartPoint().getX())) *          // 判断A是否跨过B
                            ((line1.getEndPoint().getX() - line2.getStartPoint().getX()) * (line2.getEndPoint().getY() - line2.getStartPoint().getY()) - (line1.getEndPoint().getY() - line2.getStartPoint().getY()) * (line2.getEndPoint().getX() - line2.getStartPoint().getX())) <= 0;
        } else {
            return false;
        }
    }

    public static Polygon toPolygon(Rectangle rectangle) {
        return new Polygon(
                new double[]{rectangle.x, rectangle.x + rectangle.width, rectangle.x + rectangle.width, rectangle.x},
                new double[]{rectangle.y, rectangle.y, rectangle.y + rectangle.height, rectangle.y + rectangle.height}
        );
    }

    /**
     * 多边形拟合
     */
    public static Polygon approxPolyDp(Polygon polygon, double epsilon) {
        return approxPolyDp(polygon, epsilon, true);
    }

    /**
     * 多边形拟合
     */
    public static Polygon approxPolyDp(Polygon polygon, double epsilon, boolean closed) {
        MatOfPoint2f matOfPoint2f = null;
        MatOfPoint2f out = null;
        try {
            matOfPoint2f = new MatOfPoint2f(polygon2OpencvPointArray(polygon));
            out = new MatOfPoint2f();
            Imgproc.approxPolyDP(matOfPoint2f, out, epsilon, closed);
            return opencvPoint2Polygon(out.toArray());
        } finally {
            IOUtil.close(matOfPoint2f, out);
        }
    }

    /**
     * 获取所有点的最小轮廓
     */
    public static Polygon getMinRectangle(List<Polygon> polygons) {
        List<org.opencv.core.Point> points = new LinkedList<>();

        for (Polygon polygon : polygons) {
            double[] xpoints = polygon.getXpoints(), ypoints = polygon.getYpoints();
            for (int i = 0; i < xpoints.length; i++) points.add(new org.opencv.core.Point(xpoints[i], ypoints[i]));
        }
        MatOfPoint matOfPoint = new MatOfPoint();
        matOfPoint.fromList(points);

        MatOfPoint matOfPoint1 = convexHull(matOfPoint);
        return matOfPoint2Polygon(matOfPoint1);
    }

    public static Point getCenter(double x1, double y1, double x2, double y2) {

        double minX = Math.min(x1, x2);
        double maxX = Math.max(x1, x2);
        double minY = Math.min(y1, y2);
        double maxY = Math.max(y1, y2);

        double newX = (maxX - minX) / 2 + minX;
        double newY = (maxY - minY) / 2 + minY;
        return new Point(newX, newY);
    }

    public static Point getCenter(Point p1, Point p2) {
        return getCenter(p1.x, p1.y, p2.x, p2.y);
    }

    /**
     * 判断所有点是否有任意一点被多边形包含
     */
    public static boolean include(double[] xPoints, double[] yPoints, Polygon polygon) {
        for (int i = 0; i < xPoints.length; i++) {
            if (polygon.contains(xPoints[i], yPoints[i])) {
                return true;
            }
        }
        return false;
    }

    public static boolean include(Polygon p2, Polygon polygon) {
        return include(p2.getXpoints(), p2.getYpoints(), polygon);
    }

    /**
     * 判断点是否被多边形包含
     */
    public static boolean include(Point p, Polygon polygon) {
        return polygon.contains(p);
    }

    public static boolean isIntersect(double px1, double py1, double px2, double py2,
                                      double px3, double py3, double px4, double py4) {
        boolean flag = false;
        double d = (px2 - px1) * (py4 - py3) - (py2 - py1) * (px4 - px3);
        if (d != 0) {
            double r = ((py1 - py3) * (px4 - px3) - (px1 - px3) * (py4 - py3))
                    / d;
            double s = ((py1 - py3) * (px2 - px1) - (px1 - px3) * (py2 - py1))
                    / d;
            if ((r >= 0) && (r <= 1) && (s >= 0) && (s <= 1)) {
                flag = true;
            }
        }
        return flag;
    }

    public static double Multiply(double px0, double py0, double px1, double py1,
                                  double px2, double py2) {
        return ((px1 - px0) * (py2 - py0) - (px2 - px0) * (py1 - py0));
    }

    public static boolean isPointOnLine(double px0, double py0, double px1,
                                        double py1, double px2, double py2) {
        boolean flag = false;
        double ESP = 1e-9;
        if ((Math.abs(Multiply(px0, py0, px1, py1, px2, py2)) < ESP)
                && ((px0 - px1) * (px0 - px2) <= 0)
                && ((py0 - py1) * (py0 - py2) <= 0)) {
            flag = true;
        }
        return flag;
    }

    /**
     * 移除凹缺陷
     *
     * @param polygon  目标多边形
     * @param distance 缺口最大距离
     * @param depth    缺口最大深度
     */
    public static Polygon convexRemove(Polygon polygon, double distance, int depth) {
        double[] xPoints = polygon.getXpoints();
        double[] yPoints = polygon.getXpoints();
        MatOfPoint matOfPoint = PointUtil.polygon2MatOfPoint(polygon);
        MatOfInt matOfInt = new MatOfInt();
        MatOfInt4 matOfInt4 = new MatOfInt4();
        Imgproc.convexHull(matOfPoint, matOfInt);
        if (matOfInt.total() <= 0) {
            return polygon;
        }
        Imgproc.convexityDefects(matOfPoint, matOfInt, matOfInt4);
        if (matOfInt4.total() <= 0) {
            return polygon;
        }
        int[] ints = matOfInt4.toArray();
        Set<Integer> overPoint = new HashSet<>(); // 被移除的点下标
        List<Point> points = new LinkedList<>();
        for (int i = 0; i < ints.length; i += 4) {
            int m0 = ints[i]; // 起始点（startPoint）
            int m1 = ints[i + 1]; // 结束点(endPoint)
            int m2 = ints[i + 2]; // 距离convexity hull最远点(farPoint)
            int m3 = ints[i + 3]; // 最远点到convexity hull的距离(depth)

            Point startPoint = new Point(xPoints[m0], yPoints[m0]);
            Point endPoint = new Point(xPoints[m1], yPoints[m1]);
            double dis = dis(startPoint, endPoint); // 当前缺口大小
            if (dis > distance || m3 > depth) {
                continue;
            }

            // 移除此凹缺陷区域
            for (int i1 = m0; i1 <= m1; i1++) {
                overPoint.add(i1);
            }
        }

        for (int i = 0; i < xPoints.length; i++) {
            if (overPoint.contains(i)) {
                continue;
            }
            points.add(new Point(xPoints[i], yPoints[i]));
        }

        return points2polygon(points);
    }

    private static void convexHullTest() {
        List<org.opencv.core.Point> points = new ArrayList<>();
        points.add(new org.opencv.core.Point(0, 25));
        points.add(new org.opencv.core.Point(50, 0));
        points.add(new org.opencv.core.Point(100, 25));
        points.add(new org.opencv.core.Point(100, 75));
        points.add(new org.opencv.core.Point(55, 55));
        points.add(new org.opencv.core.Point(50, 50));
        points.add(new org.opencv.core.Point(0, 75));

        MatOfPoint matOfPoint = new MatOfPoint();
        matOfPoint.fromList(points);

        MatOfPoint matOfPoint1 = convexHull(matOfPoint);
        List<org.opencv.core.Point> points1 = matOfPoint1.toList();
        System.out.println(points1);
    }

    /**
     * 凸包算法
     */
    public static Polygon convexHull(Polygon polygon) {
        return matOfPoint2Polygon(convexHull(polygon2MatOfPoint(polygon)));
    }

    /**
     * 凸包算法
     */
    public static MatOfPoint convexHull(MatOfPoint matOfPoint) {

        MatOfInt cacheIndex = new MatOfInt();
        Imgproc.convexHull(matOfPoint, cacheIndex);
        org.opencv.core.Point[] srcPointList = matOfPoint.toArray();
        List<org.opencv.core.Point> cacheList = new LinkedList<>();
        MatOfPoint result = new MatOfPoint();
        for (int i : cacheIndex.toArray()) cacheList.add(srcPointList[i]);
        result.fromList(cacheList);
        return result;
    }

    /**
     * 计算叉积
     */
    public static double cross(Point a, Point b, Point c) {
        return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
    }

    /**
     * 极角排序
     */
    public static boolean cmp(Point a, Point b, List<Point> vex) {
        int m = (int) cross(vex.get(0), a, b);
        if (m > 0) {
            return true;
        } else {
            return m == 0 && dis(vex.get(0), a) - dis(vex.get(0), b) <= 0;
        }
    }

    /**
     *
     */
    public static int relativeCCW(double x01, double y01,
                                  double x02, double y02,
                                  double px, double py) {

        x02 -= x01;
        y02 -= y01;
        px -= x01;
        py -= y01;
        double ccw = px * y02 - py * x02;
        if (ccw == 0.0) {
            ccw = px * x02 + py * y02;
            if (ccw > 0.0) {
                px -= x02;
                py -= y02;
                ccw = px * x02 + py * y02;
                if (ccw < 0.0) {
                    ccw = 0;
                }
            }
        }
        return Double.compare(ccw, 0.0);
    }

    /**
     * 判断两个矩形是否重叠
     *
     * @param r1 矩形1
     * @param r2 矩形2
     * @return true: 重叠;  false: 不重叠
     */
    public static boolean overlapRect(Rectangle2D.Double r1, Rectangle2D.Double r2) {
        return overlapRect(r1.x, r1.x + r1.width, r1.y, r1.y + r1.height,
                           r2.x, r2.x + r2.width, r2.y, r2.y + r2.height);
    }

    /**
     * 判断两个矩形是否重叠
     *
     * @param r1_x1 矩形1-x1
     * @param r1_x2 矩形1-x2
     * @param r1_y1 矩形1-y1
     * @param r1_y2 矩形1-y2
     * @param r2_x1 矩形2-x1
     * @param r2_x2 矩形2-x2
     * @param r2_y1 矩形2-y1
     * @param r2_y2 矩形2-y2
     * @return true: 重叠;  false: 不重叠
     */
    public static boolean overlapRect(double r1_x1, double r1_x2, double r1_y1, double r1_y2,
                                      double r2_x1, double r2_x2, double r2_y1, double r2_y2) {
        return !(r2_y2 < r1_y1 || r1_y2 < r2_y1 || r1_x1 > r2_x2 || r2_x1 > r1_x2);
    }

    public double[] similarity(Polygon p1, Polygon p2) {
        return similarity(p1, p2, 0.9D, 90.0D);
    }

    /**
     * 返回两个图形相似度与相差角度
     *
     * @param p1        第一个多边形
     * @param p2        第二个多边形
     * @param threshold [取值范围 0.0 - 1.0] 阈值, 当确定相似度不会大于这个值时直接跳过, 返回{0,0}
     * @param tryAngle  测试角度
     * @return double[] {相似度, p1旋转至p2的角度}
     */
    public double[] similarity(Polygon p1, Polygon p2, double threshold, double tryAngle) {
        if (p1 == null || p2 == null || p1.isEmpty() || p2.isEmpty()) {
            throw new IllegalArgumentException("p1 == null || p2 == null || p1.isEmpty() || p2.isEmpty()");
        }

        double p1Area = p1.getArea();
        double p2Area = p2.getArea();
        double v = Math.min(p1Area, p2Area) / Math.max(p1Area, p2Area);
        if (v < threshold) {
            return new double[]{0.0, 0.0};
        }

        Polygon clone = p1.clone();
        double max = 0.0D;
        double angle = 0.0D;
        for (int i = 0; i < Math.round(360 / tryAngle) - 1; i++) {
            if (i != 0) {
                clone.rotateTransform(new Point(0, 0), tryAngle);
            }
            double wSimilarity = Math.min(clone.getWidth(), p2.getWidth()) / Math.max(clone.getWidth(), p2.getWidth());
            if (wSimilarity < threshold) {
                continue;
            }
            double hSimilarity = Math.min(clone.getHeight(), p2.getHeight()) / Math.max(clone.getHeight(), p2.getHeight());
            if (hSimilarity < threshold) {
                continue;
            }

            double similarity = clone.similarity(p2, 50);
            if (similarity > max) {
                max = similarity;
                angle = i * tryAngle;
            }
        }
        return new double[]{max, angle};
    }

    /**
     * 获取移动向量
     */
    public static Vector getTranslateVector(double srcx, double srcy, double desx, double desy) {
        return new Vector(ArithUtil.sub(desx, srcx), ArithUtil.sub(desy, srcy));
    }

    /**
     * 获取移动向量
     */
    public static Vector getTranslateVector(Point src, Point des) {
        return new Vector(ArithUtil.sub(des.getX(), src.getX()), ArithUtil.sub(des.getY(), src.getY()));
    }

    /**
     * 获取缩放因子
     */
    public static double getScale(double srcSize, double desSize) {
        return ArithUtil.div(desSize, srcSize);
    }

    /**
     * 返回多边形面积
     */
    public static double getArea(double[] xPoints, double[] yPoints) {
        return Math.abs(_getArea(xPoints, yPoints) / 2.0);
    }

    /**
     * 判断多边形是否为顺时针
     */
    public static boolean clockwise(Polygon polygon) {
        return _getArea(polygon.getXpoints(), polygon.getYpoints()) > 0;
    }

    public static void offset(Point point, double angle, double l) {
        Point ret = PointUtil.rotateTransform(new Point(point.x, point.y - l), point, angle);
        point.x = ret.x;
        point.y = ret.y;
    }

    /**
     * 合并图形
     */
    public static Polygon compose(Polygon polygon, com.symaster.common.geom.entity.RotatedRect rect, int type) {

        /* 保证多边形为顺时针 */
        if (!clockwise(polygon)) {
            polygon = reversal(polygon);
        }

        List<Point> pointList = polygon.toPointList();
        com.symaster.common.geom.entity.RotatedRect.ExtendedLine extendedLine = rect.extendedLine(dis(rect.getCenter(), polygon.getCenter()));

        Integer[] indexes = new Integer[2];
        Point[] points = new Point[2];
        for (int i = 0; i < 2; i++) {
            LineSegment line = extendedLine.getLines()[i];
            Integer minIndex = null;
            Double min = null;
            Point minPoint = null;
            for (int i1 = 0; i1 < pointList.size(); i1++) {
                if (i1 > pointList.size() - 1) {
                    break;
                }
                LineSegment lineSegment = new LineSegment(
                        pointList.get(i1),
                        i1 + 1 >= pointList.size() ? pointList.get(0) : pointList.get(i1 + 1)
                );
                Point crossPoint = PointUtil.getCrossPoint(lineSegment, line);
                if (crossPoint == null) {
                    continue;
                }
                double dis = PointUtil.dis(crossPoint, rect.getCenter());
                if (min == null || dis < min) {
                    min = dis;
                    minIndex = i1;
                    minPoint = crossPoint;
                }
            }
            if (min == null) {
                throw new RuntimeException("无法连接几何关系");
            }
            indexes[i] = minIndex;
            points[i] = minPoint;
        }

        /* 删除多余顶点 */
        if (!Objects.equals(indexes[0], indexes[1])) {
            for (int i1 = 0; i1 < indexes[0] - indexes[1]; i1++) {
                pointList.remove(indexes[1] + 1);
            }
        }

        List<Point> pointList1 = rect.toPolygon().toPointList();
        pointList.add(indexes[1] + 1, points[1]);
        pointList.add(indexes[1] + 2, pointList1.get(2));
        pointList.add(indexes[1] + 3, pointList1.get(3));
        pointList.add(indexes[1] + 4, points[0]);
        return PointUtil.points2polygon(pointList);
    }

    /**
     * 将两个多边形合并
     * <p>
     * 并集，OR (union)
     */
    public static Polygon compose(Polygon p1, Polygon p2) {
        if (p1.isEmpty()) {
            throw new IllegalArgumentException("p1 is empty");
        }
        if (p2.isEmpty()) {
            throw new IllegalArgumentException("p2 is empty");
        }
        Path clipperPathP1 = p1.toClipperPath();
        Path clipperPathP2 = p2.toClipperPath();

        DefaultClipper defaultClipper = new DefaultClipper();
        defaultClipper.addPath(clipperPathP1, Clipper.PolyType.SUBJECT, true);
        defaultClipper.addPath(clipperPathP2, Clipper.PolyType.CLIP, true);
        Paths retPaths = new Paths();
        boolean execute = defaultClipper.execute(Clipper.ClipType.UNION, retPaths);
        if (!execute) {
            throw new IllegalArgumentException("合并多边形失败, Clipper执行异常");
        }
        if (retPaths.size() != 1) {
            throw new IllegalArgumentException("合并多边形失败");
        }
        Path longPoints = retPaths.get(0);
        return longPoints.toPolygon();
    }

    /**
     * 反转多边形顶点顺序
     */
    public static Polygon reversal(Polygon polygon) {
        List<Point> pointList = polygon.toPointList();
        List<Point> result = new ArrayList<>(pointList.size());
        for (int i = pointList.size() - 1; i >= 0; i--) {
            result.add(pointList.get(i));
        }
        return points2polygon(result);
    }

    private static double _getArea(double[] xPoints, double[] yPoints) {
        double area = 0.0D;
        int len = xPoints.length;
        for (int i = 1; i <= len; i++) {
            double x = xPoints[i - 1];
            double y = yPoints[i - 1];
            double nextX = xPoints[i % len];
            double nextY = yPoints[i % len];
            double temp = x * nextY - nextX * y;
            area += temp;
        }
        return area;
    }

    /**
     * 返回多边形面积
     */
    public static double getArea(Polygon polygon) {
        return getArea(polygon.getXpoints(), polygon.getYpoints());
    }

    /**
     * 顶点剔除
     */
    public static Polygon pointOpt(double[] xpoints, double[] ypoints, double epsilon) {
        return pointOpt(new Polygon(xpoints, ypoints), epsilon);
    }

    public static Polygon autoPointOpt(Polygon polygon) {
        double v = polygon.getWidth() * polygon.getHeight();
        double v1 = v * 0.00005;
        return pointOpt(polygon, v1);
    }

    /**
     * 顶点剔除
     *
     * @param polygon 顶点集
     * @param epsilon 剔除阈值
     */
    public static Polygon pointOpt(Polygon polygon, double epsilon) {

        List<Point> points = polygon2points(polygon);

        // 调用本方法的重载方法，  -执行顶点剔除
        List<Point> temp = new ArrayList<>(points);
        if (epsilon != 0) {
            temp = pointOpt(points, epsilon);
        }

        List<Point> result = new ArrayList<>(temp.size());
        for (Point pointItem : temp) {
            if (result.contains(pointItem)) {
                continue;
            }
            result.add(pointItem);
        }

        return points2polygon(result);
    }

    /**
     * 顶点剔除
     *
     * @param pointList 顶点集
     * @param epsilon   剔除阈值
     */
    public static List<Point> pointOpt(List<Point> pointList, double epsilon) {

        double maxEps = 0; // 最大距离
        int maxIndex = 0; // 最大距离的索引
        int end = pointList.size() - 1; // 末尾索引

        // 循环遍历获得与线距离最大的顶点
        for (int i = 1; (i < end); i++) {
            // 点与线的垂直距离
            Point point01 = pointList.get(i);
            Point point02 = pointList.get(0);
            Point point03 = pointList.get(end);
            double p = perpendicularDistance(point01, point02, point03);
            if (p > maxEps) {
                maxEps = p;
                maxIndex = i;
            }
        }

        // 返回集
        List<Point> resultPoints = new ArrayList<>();

        // 大于给定阈值，
        if (maxEps > epsilon) {
            List<Point> po1 = pointOpt(new ArrayList<>(pointList).subList(0, maxIndex), epsilon);
            List<Point> po2 = pointOpt(new ArrayList<>(pointList).subList(maxIndex, end + 1), epsilon);

            // 构造结果集 前半部分
            resultPoints.addAll(po1);

            // 构造结果集 后半部分
            resultPoints.addAll(po2);
        } else {

            // 点与线的距离小于给定阈值， -剔除两点（线）之间的所有点
            resultPoints.add(pointList.get(0));
            resultPoints.add(pointList.get(end));
        }

        return resultPoints;
    }

    /**
     * 获取移动向量
     */
    public static Vector getTranslateVector(Polygon src, Polygon des) {
        return new Vector(ArithUtil.sub(des.getX(), src.getX()), ArithUtil.sub(des.getY(), src.getY()));
    }

    /**
     * 平移变换
     */
    public static void translateTransform(double[] xpoitns, double[] ypoints, Vector vector) {
        for (int i = 0; i < xpoitns.length; i++) {
            xpoitns[i] += vector.getX();
            ypoints[i] += vector.getY();
        }
    }

    /**
     * 平移变换
     */
    public static void translateTransform(Polygon polygon, Vector vector) {
        double[] xpoints = polygon.getXpoints();
        double[] ypoints = polygon.getYpoints();
        translateTransform(xpoints, ypoints, vector);
    }

    /**
     * 旋转 中心点旋转
     *
     * @param polygon 多边形
     * @param angle   旋转角度
     */
    public static Polygon rotateTransform(Polygon polygon, double angle) {
        return rotateTransform(polygon, angle, polygon.getCenter());
    }

    /**
     * 旋转
     *
     * @param polygon 多边形
     * @param angle   旋转角度
     * @param center  旋转中兴点
     */
    public static Polygon rotateTransform(Polygon polygon, double angle, Point center) {
        double[] xPoints = polygon.getXpoints();
        double[] yPoints = polygon.getYpoints();

        double[] retXs = new double[xPoints.length];
        double[] retYs = new double[yPoints.length];

        for (int i = 0; i < xPoints.length; i++) {
            double[] doubles = rotateTransform(xPoints[i], yPoints[i], center.x, center.y, angle);
            retXs[i] = doubles[0];
            retYs[i] = doubles[1];
        }
        return new Polygon(retXs, retYs);
    }

    /**
     * 调整角度值
     * -90 -> 270
     * 380 -> 20
     * 360 -> 0
     *
     * @param srcAngle 原角度值
     */
    public static double adjustAngle(double srcAngle) {
        if (srcAngle >= 360) {
            srcAngle -= 360;
            return adjustAngle(srcAngle);
        }
        if (srcAngle < 0) {
            srcAngle += 360;
            return adjustAngle(srcAngle);
        }
        return srcAngle;
    }

    /**
     * 旋转
     *
     * @param p1     目标点
     * @param center 旋转中心点
     * @param angle  角度
     * @return 旋转后的结果点
     */
    public static Point rotateTransform(Point p1, Point center, double angle) {
        double[] doubles = rotateTransform(p1.x, p1.y, center.x, center.y, angle);
        return new Point(doubles[0], doubles[1]);
    }

    /**
     * 旋转
     *
     * @param x       目标点x
     * @param y       目标点y
     * @param centerX 旋转中心点x
     * @param centerY 旋转中心点y
     * @param angle   旋转角度
     * @return 旋转后的结果[x, y]
     */
    public static double[] rotateTransform(double x, double y, double centerX, double centerY, double angle) {
        double radians = Math.toRadians(angle);
        double newX = centerX + (x - centerX) * Math.cos(radians) - (y - centerY) * Math.sin(radians);
        double newY = centerY + (x - centerX) * Math.sin(radians) + (y - centerY) * Math.cos(radians);
        return new double[]{newX, newY};
    }

    /**
     * 等比例缩放
     */
    public static Polygon scaleTransform(Polygon polygon, double scalex, double scaley) {
        PathIterator pathIterator = polygon.getPathIterator(AffineTransform.getScaleInstance(scalex, scaley));
        return pathIterator2Polygon(pathIterator, polygon.getNpoints());
    }

    /**
     * 计算两点之间的距离
     *
     * @param x1 点1x
     * @param y1 点1y
     * @param x2 点2x
     * @param y2 点2y
     */
    public static double dis(double x1, double y1, double x2, double y2) {
        return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
    }

    /**
     * 计算两点之间的距离
     *
     * @param p1 点1
     * @param p2 点2
     */
    public static double dis(Point p1, Point p2) {
        return dis(p1.x, p1.y, p2.x, p2.y);
    }

    /**
     * ChaikinSmoothing 坐标平滑
     *
     * @param refinements 平滑次数
     */
    public static Polygon chaikinSmoothing(Polygon polygon, int refinements) {
        if (refinements < 1) {
            throw new IllegalArgumentException("refinements(平滑次数) 不应该小于1");
        }
        List<Point> points = polygon2points(polygon);
        for (int i = 0; i < refinements; i++) {
            List<Point> cachePoints = new LinkedList<>();
            for (int j = 0; j < points.size(); j++) {
                Point p1 = points.get(j);
                Point p2 = points.get((j + 1 >= points.size() ? 0 : j + 1));
                Point Q = findQPointPosition(p1, p2);
                Point R = findRPointPosition(p1, p2);
                cachePoints.add(Q);
                cachePoints.add(R);
            }
            points = cachePoints;
        }
        return points2polygon(points);
    }

    /**
     * 将路径迭代器转为Polygon
     */
    public static Polygon pathIterator2Polygon(PathIterator patIterator, int len) {
        double[] iix = new double[len];
        double[] iiy = new double[len];
        double[] xAndY = new double[2];
        for (int i = 0; !patIterator.isDone() && i < len; ) {
            int type = patIterator.currentSegment(xAndY);
            if (type != PathIterator.SEG_CLOSE) {
                iix[i] = xAndY[0];
                iiy[i] = xAndY[1];
                i++;
            }
            patIterator.next();
        }
        return new Polygon(iix, iiy);
    }

    /**
     * 对象转换
     * List<Point> to Polygon
     */
    public static Polygon points2polygon(List<Point> points) {

        double[] xpoints = new double[points.size()];
        double[] ypoints = new double[points.size()];
        for (int i = 0; i < points.size(); i++) {
            xpoints[i] = points.get(i).getX();
            ypoints[i] = points.get(i).getY();
        }

        return new Polygon(xpoints, ypoints);
    }

    /**
     * 对象转换
     * Polygon to List<Point>
     */
    public static List<Point> polygon2points(Polygon polygon) {
        double[] xpoints = polygon.getXpoints();
        double[] ypoints = polygon.getYpoints();
        List<Point> result = new ArrayList<>(xpoints.length);

        for (int i = 0; i < xpoints.length; i++) {
            Point point = new Point(xpoints[i], ypoints[i]);
            if (!result.contains(point)) {
                result.add(point);
            }
        }
        return result;
    }

    /**
     * 对象转换
     * MatOfPoint to Polygon
     */
    public static Polygon matOfPoint2Polygon(MatOfPoint matOfPoint) {
        return opencvPoint2Polygon(matOfPoint.toArray());
    }

    public static Polygon opencvPoint2Polygon(org.opencv.core.Point... points) {
        double[] xPoints = new double[points.length], yPoints = new double[points.length];
        for (int i = 0; i < points.length; i++) {
            xPoints[i] = points[i].x;
            yPoints[i] = points[i].y;
        }
        return new Polygon(xPoints, yPoints);
    }

    /**
     * 对象转换
     * Polygon to MatOfPoint
     */
    public static MatOfPoint polygon2MatOfPoint(Polygon polygon) {
        MatOfPoint result = new MatOfPoint();
        result.fromArray(polygon2OpencvPointArray(polygon));
        return result;
    }

    public static org.opencv.core.Point[] polygon2OpencvPointArray(Polygon polygon) {
        double[] xPoints = polygon.getXpoints(), yPoints = polygon.getYpoints();
        org.opencv.core.Point[] points = new org.opencv.core.Point[xPoints.length];
        for (int i = 0; i < xPoints.length; i++) {
            points[i] = new org.opencv.core.Point(xPoints[i], yPoints[i]);
        }
        return points;
    }

    private static Point findQPointPosition(Point p1, Point p2) {
        Point sand1 = p1.multiplier(0.75);
        Point sand2 = p2.multiplier(0.25);
        return sand1.sum(sand2);
    }

    private static Point findRPointPosition(Point p1, Point p2) {
        Point sand1 = p1.multiplier(0.25);
        Point sand2 = p2.multiplier(0.75);
        return sand1.sum(sand2);
    }

    private static double perpendicularDistance(Point point, Point start, Point end) {
        Line2D.Double line = new Line2D.Double(start.getX(), start.getY(), end.getX(), end.getY());
        return line.ptLineDist(new Point2D.Double(point.getX(), point.getY()));
    }

    public static java.awt.Polygon toAwtPolygon(Polygon p1) {
        double[] xPoints = p1.getXpoints();
        double[] yPoints = p1.getYpoints();
        int[] intXPoints = Arrays.stream(xPoints).mapToInt(value -> (int) Math.round(value)).toArray();
        int[] intYPoints = Arrays.stream(yPoints).mapToInt(value -> (int) Math.round(value)).toArray();
        return new java.awt.Polygon(intXPoints, intYPoints, intXPoints.length);
    }
}